#include "stdafx.h"
#include "BrassicaPrivate.h"

#define GetBase(x) (VOID*)((UINT_PTR)x & (UINT_PTR)(0 - PageSize)) //haxx - gets base address

//Last sizeof(void*) bytes at the end of each block hold a pointer to the next block

// class BRASSICA_API Pipe
VOID *Pipe::allocatepage(){
	BYTE *ptr = (BYTE*)VirtualAlloc(NULL, PageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	if (!ptr){
		throw "Error allocating virtual memory";
	}
	return ptr;
}
VOID Pipe::freepage(VOID *oldpage){
	if (!VirtualFree(GetBase(oldpage), 0, MEM_RELEASE)){
		throw "Error freeing virtual memory";
	}
}
Pipe::Pipe(PipeIn **pPipeIn, PipeOut **pPipeOut){
	SYSTEM_INFO si;
	GetSystemInfo(&si);
	PageSize = si.dwAllocationGranularity; 
	WriteHead = VirtualAlloc(NULL, PageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	if (!WriteHead){
		throw "Error allocating virtual memory";
	}
	ReadHead = WriteHead;
	if ((hMutex = CreateMutex(NULL, FALSE, NULL)) == NULL){
		throw "Error creating mutex";
	}
	
	*pPipeIn = (inpipe = new PipeIn(this));
	*pPipeOut = (outpipe = new PipeOut(this));
}
Pipe::~Pipe(){
	//Get the mutex
	if (WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0){
		throw "Error securing mutex";
	}
	
	//Run from read head to write head, freeing all the blocks as we go
	while (GetBase(ReadHead) != GetBase(WriteHead)){ //while they're not in the same block
		if (!VirtualFree(GetBase(ReadHead), 0, MEM_RELEASE)){
			throw "Error freeing virtual memory";
		}
		ReadHead = (VOID*)*((VOID**)((UINT_PTR)GetBase(ReadHead) + PageSize + sizeof(VOID*)));
	}

	if (!VirtualFree(GetBase(ReadHead), 0, MEM_RELEASE)){
		throw "Error freeing virtual memory";
	}

	if (!ReleaseMutex(hMutex)){
		throw "Error releasing mutex";
	}

	if (!CloseHandle(hMutex)){
		throw "Error closing mutex handle";
	}

	delete inpipe;
	delete outpipe;
}
VOID Pipe::Write(VOID* data, SIZE_T datalen){
	//Get the mutex
	if (WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0){
		throw "Error securing mutex";
	}
	//SIZE_T strmemlen = _tcsclen(msg) * sizeof(TCHAR);
	while (GetBase((UINT_PTR)WriteHead + datalen + sizeof(VOID*)) != GetBase(WriteHead)){ //i.e. wraps over a page
		SIZE_T memchunk = (UINT_PTR)GetBase(WriteHead) + PageSize - sizeof(VOID*) - (UINT_PTR)WriteHead;
		CopyMemory(WriteHead, data, memchunk);
		data = (VOID*)((UINT_PTR)data + memchunk);
		WriteHead = (VOID*)((UINT_PTR)GetBase(WriteHead) + PageSize - sizeof(VOID*));
		*((VOID**)WriteHead) = allocatepage();
		WriteHead = *(VOID**)WriteHead;
		datalen -= memchunk;
	}

	//copy last bit
	CopyMemory(WriteHead, data, datalen);
	WriteHead = (VOID*)((UINT_PTR)WriteHead + datalen);

	if (!ReleaseMutex(hMutex)){
		throw "Error releasing mutex";
	}
}
SIZE_T Pipe::Read(VOID* buffer, SIZE_T maxlen){
	//Get the mutex
	if (WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0){
		throw "Error securing mutex";
	}

	SIZE_T BytesRead = 0;

	//like above, only in reverse

	while (TRUE){
		VOID *base = GetBase((UINT_PTR)ReadHead + maxlen + sizeof(VOID*));
		if (GetBase(ReadHead) == GetBase(WriteHead)){
			if ((UINT_PTR)ReadHead + maxlen > (UINT_PTR)WriteHead){
				maxlen = (UINT_PTR)WriteHead - (UINT_PTR)ReadHead;
				break;
			}
		}
		if (base == GetBase(ReadHead)) break; // no need for another block
		SIZE_T memchunk = (UINT_PTR)GetBase(ReadHead) + PageSize - sizeof(VOID*) - (UINT_PTR)ReadHead;
		CopyMemory(buffer, ReadHead, memchunk);
		buffer = (VOID*)((UINT_PTR)buffer + memchunk);
		VOID *t = ReadHead;
		ReadHead = *((VOID**)((UINT_PTR)GetBase(ReadHead) + PageSize - sizeof(VOID*)));
		freepage(t);
		maxlen -= memchunk;
		BytesRead += memchunk;
	}

	//last bit
	CopyMemory(buffer, ReadHead, maxlen);
	ReadHead = (VOID*)((UINT_PTR)ReadHead + maxlen);
	BytesRead += maxlen;

	if (!ReleaseMutex(hMutex)){
		throw "Error releasing mutex";
	}
	return BytesRead;
}

// class BRASSICA_API PipeIn
PipeIn::PipeIn(Pipe *BasePipe){
	pipe = BasePipe;
}
VOID PipeIn::Write(VOID* data, SIZE_T datalen){
	pipe->Write(data, datalen);
}

// class BRASSICA_API PipeOut
PipeOut::PipeOut(Pipe *BasePipe){
	pipe = BasePipe;
}
SIZE_T PipeOut::Read(VOID* buffer, SIZE_T maxlen){
	return pipe->Read(buffer, maxlen);
}
